package cn.cuilan.common.pipeline;

import cn.cuilan.common.codec.MessageDecoder;
import cn.cuilan.common.codec.MessageEncoder;
import cn.cuilan.common.core.ConnectorConfiguration;
import cn.cuilan.common.core.ConnectorConfigurer;
import io.netty.bootstrap.AbstractBootstrap;
import io.netty.channel.*;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.timeout.IdleStateHandler;

/**
 * 自定义私有协议初始化处理器
 *
 * @param <T> Channel
 * @param <B> ServerBootStrap or BootStrap
 * @param <C> ServerConfiguration or ClientConfiguration
 * @author zhang.yan
 */
public class ProtocolChannelInitializer<
        T extends Channel,
        B extends AbstractBootstrap<B, T>,
        C extends ConnectorConfiguration> extends ChannelInitializer<T> {

    /**
     * 配置信息类
     */
    private final C configuration;

    /**
     * 连接配置器
     */
    private final ConnectorConfigurer<C, B> configurer;

    /**
     * 工作线程组
     */
    private final EventLoopGroup workerGroup;

    /**
     * 自定义处理集
     */
    private final ChannelHandler[] customChannelHandlers;

    /**
     * 消息编码器
     */
    private final MessageEncoder messageEncoder;

    /**
     * 日志处理器
     */
    private final ChannelHandler loggingHandler;

    /**
     * 解析失败异常处理器
     */
    private final ChannelHandler parseExceptionHandler;

    public ProtocolChannelInitializer(
            C configuration,
            ConnectorConfigurer<C, B> configurer,
            EventLoopGroup workerGroup,
            ChannelHandler... customChannelHandlers) {
        this.configuration = configuration;
        this.configurer = configurer;
        this.workerGroup = workerGroup;
        this.customChannelHandlers = customChannelHandlers;

        this.messageEncoder = createMessageEncoder(configuration);
        this.loggingHandler = createLoggingHandler(configuration);
        this.parseExceptionHandler = createParseExceptionHandler();
    }

    @Override
    protected void initChannel(T channel) throws Exception {
        final ChannelPipeline pipeline = channel.pipeline();

        final int lengthFieldLength = configuration.getFrameLengthFieldLength();
        // 定长解码器
        pipeline.addLast("LengthFieldFrameDecoder",
                new LengthFieldBasedFrameDecoder(
                        configuration.getMaxFrameLength(),
                        configuration.getFrameLengthFieldOffset(),
                        lengthFieldLength,
                        configuration.getFrameLengthFieldAdjust(),
                        lengthFieldLength));
        // 日志处理器
        if (configuration.addLoggingHandler()) {
            pipeline.addLast(workerGroup, "loggingHandler", loggingHandler);
        }
        // 消息解码器
        pipeline.addLast("messageDecoder", createMessageDecoder());
        // 消息编码器
        pipeline.addLast("messageEncoder", messageEncoder);
        // 异常解析器
        if (configuration.replyOnError()) {
            pipeline.addLast(workerGroup, "replyOnError", parseExceptionHandler);
        }
        // 闲置状态处理器
        pipeline.addLast("idleState", new IdleStateHandler(0, 0, configuration.getIdleTimeout()));
        // 闲置事件处理器
        pipeline.addLast("idleEventHandler", new IdleEventHandler());
        // 自定义其它处理器
        if (customChannelHandlers != null) {
            pipeline.addLast(workerGroup, customChannelHandlers);
        }
        // 其余配置
        if (configurer != null) {
            configurer.configurePipeline(pipeline, configuration);
        }
    }

    private MessageEncoder createMessageEncoder(C configuration) {
        // 取默认长度所占字节数
        return new MessageEncoder(configuration.getFrameLengthFieldLength());
    }

    private MessageDecoder createMessageDecoder() {
        return new MessageDecoder();
    }

    private ChannelHandler createLoggingHandler(C configuration) {
        return new MessageLoggingHandler(LogLevel.INFO,
                configuration.logSensitiveData(),
                configuration.logFieldDescription(),
                configuration.getSensitiveDataFields());
    }

    private ChannelHandler createParseExceptionHandler() {
        return new ParseExceptionHandler(true);
    }
}
